#include "ota.h"
#include <glib.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include "log.h"

static uint8_t*     firmware_data = NULL;

static FIRMWARE_INFO    firmware_info = {
    .firmwareVer = 0,
    .firmwareSize = 0,
    .tag = {0},
};

static pthread_mutex_t     ota_mutex;

// 当前升级进度
static uint32_t		current_pos = 0;
static time_t		last_ota_t;

void InitOTA()
{
    pthread_mutex_init(&ota_mutex, NULL);
}

bool loadOTAFile(struct cli_def *cli, const char* firmwareFile)
{
    bool            ret = true;
    int             ota_fd, ota_md5_fd;
    char*           ota_md5_file;
    struct stat     filestat;
    int             r;
    uint8_t*        otaData = NULL;
    uint32_t        otaVer = 0;
    uint8_t         otaTag[16];
    uint8_t         otaTag_File[16];
    long            otaSize = 0;
    char            tempString[100];
    int             i;

    ota_fd = open(firmwareFile, O_RDONLY);
    if (ota_fd == -1) {
        cli_print(cli, "Open '%s' failed! Error:%s\r\n", firmwareFile, strerror(errno));
        return false;
    }

    r = fstat(ota_fd, &filestat);
    if (r != 0) {
        close(ota_fd);
        cli_print(cli, "Get '%s' size failed! Error:%s\r\n", firmwareFile, strerror(errno));
        return false;
    } else {
        otaSize = filestat.st_size;
        // 不超过640KB（压缩后的)
        if (otaSize <= 655350) {
            otaData = malloc(otaSize);
            read(ota_fd, otaData, otaSize);
            close(ota_fd);
            cli_print(cli, "'%s' read into memory.\r\n", firmwareFile);
        } else {
            close(ota_fd);
            free(otaData);
            cli_print(cli, "'%s' size too large:%d bytes.\r\n", firmwareFile, otaSize);
            return false;
        }
    }

    // 读取MD5文件
    ota_md5_file = malloc(strlen(firmwareFile) + 4 + 1);
    sprintf(ota_md5_file, "%s.md5", firmwareFile);
    ota_md5_fd = open(ota_md5_file, O_RDONLY);
    if (ota_md5_fd == -1) {
        cli_print(cli, "Open '%s' failed! Error:%s\r\n", ota_md5_file, strerror(errno));
        free(otaData);
        return false;
    }
    r = read(ota_md5_fd, tempString, 32 + 1 + 8);

    // 读版本
    

    close(ota_md5_fd);
    if (r != (32 + 1 + 8)) {
        cli_print(cli, "Read '%s' failed!\r\n", ota_md5_file);
        free(otaData);
        return false;
    }

    // md5 tag
    for (i = 0; i < 16; i++) {
        char    hex[3];
        int     tempInt;

        hex[0] = tempString[i * 2];
        hex[1] = tempString[i * 2 + 1];
        hex[2] = 0;

        r = sscanf(hex, "%X", &tempInt);
        if (r != 1) {
            tempInt = 0;
        }

        otaTag_File[i] = (uint8_t)tempInt;
    }

    // ver
    int		tempInts[4];

    for (i = 0; i < 4; i++) {
	char	hex[3];
	int 	tempInt;

	hex[0] = tempString[0x21 + i * 2];
	hex[1] = tempString[0x21 + i * 2 + 1];
	hex[2] = 0;

    	r = sscanf(hex, "%x", &tempInts[i]);
	if (r != 1) {
	    tempInts[i] = 0;
	}
    }

    otaVer = (tempInts[0] << 24) + (tempInts[1] << 16) + (tempInts[2] << 8) + tempInts[3];

    cli_print(cli, "otaVer = %d", otaVer);

    // 计算确认MD5是否一致
    GChecksum*  md5sum;
    gsize       tagSize = sizeof(otaTag);
    
    md5sum = g_checksum_new(G_CHECKSUM_MD5);
    g_checksum_update(md5sum, otaData, otaSize);
    g_checksum_get_digest(md5sum, otaTag, &tagSize);

    g_checksum_free(md5sum);

    if (memcmp(otaTag, otaTag_File, 16) != 0) {
        cli_print(cli, "MD5 check failed!");
        free(otaData);
        return false;
    } else {
        cli_print(cli, "OTA loaded & checked!");
    }

    // 打印信息
    char    md5_buffer[33];
    cli_print(cli, "Filename:%s", firmwareFile);
    cli_print(cli, "Ver:%08X", otaVer);
    cli_print(cli, "Size:%d Bytes", otaSize);
    for (i = 0; i < 16; i++) {
        sprintf(md5_buffer + (i * 2), "%02X", otaTag[i]);
    }
    cli_print(cli, "MD5Sub:%s", md5_buffer);

    pthread_mutex_lock(&ota_mutex);

    // 将数据拷贝到最终的参数中
    if (firmware_data) {
        free(firmware_data);
    }

    firmware_data = malloc(otaSize);
    memcpy(firmware_data, otaData, otaSize);
    firmware_info.firmwareVer = otaVer;
    firmware_info.firmwareSize = otaSize;
    memcpy(firmware_info.tag, otaTag, 16);

    free(otaData);

	current_pos = 0;
	last_ota_t = time(NULL);

    pthread_mutex_unlock(&ota_mutex);

    return ret;
}

bool GetFirmwareInfo(FIRMWARE_INFO* info)
{
    bool      newFirmware = false;

    // 使用cli进行设置（注意线程安全问题，CLI和LoRA不是同一个线程）
    pthread_mutex_lock(&ota_mutex);

    if (firmware_data) {
        newFirmware = true;
        *info = firmware_info;
    }

    pthread_mutex_unlock(&ota_mutex);

    return newFirmware;
}

uint8_t cmd_GetFirmware(uint32_t pos, uint8_t no, uint8_t* sendBuffer)
{
    // 从POS计算获取数据
    uint32_t        start;
    uint32_t        dataSize = 0;
    uint8_t         packSize = 0;
    
    pthread_mutex_lock(&ota_mutex);

    do {
        start = pos * LORA_OTA_PACKET_SIZE;
        
		current_pos = pos;
		last_ota_t = time(NULL);

        if (start >= firmware_info.firmwareSize) {
            // 错误，请求位置超出文件大小
            packSize = 0;
            break;
        }

        dataSize = firmware_info.firmwareSize - start;
        if (dataSize > LORA_OTA_PACKET_SIZE) {
            dataSize = LORA_OTA_PACKET_SIZE;
        }

        packSize = packFirmwareData(firmware_data + start, dataSize, no, sendBuffer, MAX_LORA_BUFFER_SIZE);
    } while (0);


    pthread_mutex_unlock(&ota_mutex);

    log_print(LOG_INFO, "OTA:====pos:%d   size:%d\r\n", pos, dataSize);

    return packSize;
}

bool FinishOTA()
{
    // 取消
    pthread_mutex_lock(&ota_mutex);

    if (firmware_data) {
        free(firmware_data);
        firmware_data = NULL;
    }

    firmware_info.firmwareSize = 0;
    firmware_info.firmwareVer = 0;
    memset(firmware_info.tag, 0, sizeof(firmware_info.tag));

    pthread_mutex_unlock(&ota_mutex);

    return false;
}

bool OTAStop()
{
    FinishOTA();
}

bool GetOTAStatus(time_t*  t, uint32_t* pos, uint16_t* per)
{
	// 时间 
	*t = last_ota_t;

	// 大小
	*pos = current_pos * LORA_OTA_PACKET_SIZE;

	*per = firmware_info.firmwareSize > 0 ? ((float)*pos  * 100/ (float)firmware_info.firmwareSize) : 0;

	return true;
}
